from pyeasyaccept.result.ScriptResultManager import ScriptResultManager
from pyeasyaccept.script.ParsedLineReader import ParsedLineReader 
from pyeasyaccept.script.ParsedLine import ParsedLine
from pyeasyaccept.result.Result import Result
from pyeasyaccept.commands.InternalCommands import InternalCommands
from pyeasyaccept.commom.Configuration import Configuration
from pyeasyaccept.events.PyEAErrorEvent import PyEAErrorEvent
import sys
"""
This class is the core of the Python EasyAccept.

The Script class must be instantiated for each test script to be executed. 
A test script is a sequence of commands present in a test file. 
A script may consist of two types of commands: internal and external. 
The internal commands are part of Python EasyAccept itself. 
For example of these commands: Expect, Echo, and so on.
Each of these internal commands is explained in the corresponding "xxxCommand" class. 
The external commands are those available through the python application facade, providing access to the functionality of 
the software being tested. Basically, any public method available in the facade can be called in the script. 
The returned value can then be tested (using an internal command such as Expect).
Once a script is created, it can be executed. 
Script results can be obtained about the execution of script's commands throw the PythonEasyAccept API.

@author: Gustavo Pereira
@author: Magno Jefferson
"""
class Script:
    
    internalCommands = InternalCommands()
    
    def __init__(self, facade, file):
        self.facade = facade
        self.file = file
        self.scriptResultManager = ScriptResultManager(file)
        self.parsedLineReader = ParsedLineReader(file)
        self.parsedLine = ParsedLine()
        self.errorListeners = []
        self.errorsToReport = sys.maxint
        
    def addErrorListener(self, listener):
        self.errorListeners.append(listener)
    
    def setNumberErrorsToReport(self,errorsToReport):
        self.errorsToReport = errorsToReport
        
    def runScript(self):
        """
        This method runs the script tests
        @param errorListeners: the error listeners.
        """
        line = 1
        errors = 0
        oneResult = self.getAndExecuteCommand()
        
        while (oneResult != None):
            if(self.isValidTestLine()):
                oneResult.setLine(line)

                if (oneResult.getTestLineStatus() == Configuration.ERROR_CODE):
                    self.fireErrorEvent(oneResult.getErrorMessage())
                    errors += 1
                    if (self.errorsToReport == 0 and errors > 0):
                        break

                self.scriptResultManager.addResult(oneResult)
            line += 1    
            oneResult = self.getAndExecuteCommand()
            if (errors >= self.errorsToReport and errors != 0):
                break           
            
    def fireErrorEvent(self, msg):
        """
        It fires the error event.
        @param errorListeners: the error listeners.
        @param msg: the message of the event.
        """
        for listener in self.errorListeners:
            listener.notifyPyEAErrorEvent(PyEAErrorEvent(msg))
              
    def isValidTestLine(self):
        """
        This method returns a boolean value that informs if the line is valid or not.
        @return: a boolean value that informs if the line is valid or not.
        """  
        return not self.isComment(self.parsedLine) and not self.isWhiteLine(self.parsedLine) and not(self.isEcho(self.parsedLine))
    
    def isEcho(self, parsedLine):
        """
        This method returns a boolean value that informs if the line command is the internal ECHO command.
        @return: a boolean value that informs if the line command is the internal ECHO command.
        """ 
        if(parsedLine == None):
            return False;
        else:
            if(len(parsedLine.getLineParametersList()) == 0):
                return False
                    
        return Configuration.ECHO_CODE in parsedLine.getParameter(0)
    
    def getScriptResultManager(self):
        """
        This method returns the script result manager of this script.
        @return: the script result manager of this script.
        """ 
        return self.scriptResultManager
    
    def getAndExecuteCommand(self):
        """
        This method submits the test script to be executed
        @return: a Result with the status code "PASSED" if the test pass, a Result with 
        the status code "FAILURE" otherwise.
        """        
        self.parsedLine = self.parsedLineReader.getParsedLine()     
        
        if(self.isEndOfFile(self.parsedLine)):
            return None; 
        else:
            if(self.isComment(self.parsedLine)): 
                return Configuration.COMMENT_CODE
            else:
                if(self.isWhiteLine(self.parsedLine)):
                    return Configuration.WHITE_LINE_CODE
        
        return self.executeCommand(self.parsedLine)
     
    def isComment(self, parsedLine):
        """
        This method returns a boolean value that informs if the line is a comment.
        @param parsedLine: the parsed line
        @return: a boolean value that informs if the line is a comment.
        """ 
        if(parsedLine == None):
            return False;
        else:
            if(len(parsedLine.getLineParametersList()) == 0):
                return False
                    
        return Configuration.COMMENT_CODE in parsedLine.getParameter(0)
    
    def isWhiteLine(self, parsedLine):
        """
        This method returns a boolean value that informs if the parsed line is a white line.
        @param parsedLine: the parsed line
        @return: a boolean value that informs if the parsed line is a white line.
        """ 
        if(parsedLine == None):
            return False;
        else:
            if(len(parsedLine.getLineParametersList()) == 0):
                return Configuration.WHITE_LINE_CODE
    
    def isEndOfFile(self, parsedLine):
        """
        This method returns a boolean value that informs if the end of file was reached.
        @param parsedLine: the parsed line
        @return: a boolean value that informs if the end of file was reached.
        """ 
        return self.parsedLine == None 
          
    def executeCommand(self, parsedLine):
        """
        This method executes the command defined in the parsed line
        @param parsedLine: the parsed line to be executed
        @return: the execution result
        """
        assert parsedLine.getNumberOfParameters() > 0 , "Coudn't have an empty line  as a script test line."
        
        command = parsedLine.getParameter(0)
        
        try:
            if self.isInternalCommand(command):
                result = self.executeInternalCommand(parsedLine)
            else:
                result = self.executeExternalCommand(parsedLine)      
        except Exception, e:            
            exceptionMsg = str(e)
            result = Result(parsedLine, Configuration.ERROR_CODE, exceptionMsg)
            result.addException(None, exceptionMsg)
                
        return result

    def isInternalCommand(self, command):
        """
        This method returns a boolean value that informs if the specified command is an internal command
        @param command: tthe command to be analised
        @return: a boolean value that informs if the specified command is an internal command
        """ 
        return self.internalCommands.containsCommand(command.lower())
         
    def executeExternalCommand(self, parsedLine):
        """
        This method executes an external command defined in the parsed line
        @param parsedLine: the parsed line to be executed
        @return: the execution result
        """  
        appCommand = parsedLine.getParameter(0)
        
        appParameters = parsedLine.subLine(1)
        
        try:
            if(parsedLine.getNumberOfParameters > 1): #has parameter(s)
                appMethodResult = self.execute(appCommand, appParameters)
            else: #hasn't parameters
                appMethodResult = self.execute(appCommand, [])
        except TypeError, e:
                result = Result(parsedLine, Configuration.ERROR_CODE, str(e))
                result.addException(None, str(e))
                return result
            
        
        except Exception, e:
                result = Result(parsedLine, Configuration.ERROR_CODE, str(e))
                result.addException(None, str(e))
                return result

            
        return Result(parsedLine, Configuration.SUCCESS_CODE, appMethodResult)
             
    def execute(self, method, parameters):
        """
        With reflection, this method executes an operation on the python application facade
        @param method: the method to be executed
        @param parameters: the method parameters
        @return: the execution result
        """     
        func = getattr(self.facade, method, None)
        
        if(func):
            try:
                if(parameters.__len__() > 0):
                    result = func( *parameters )
                    # se nao existe: raise Exception("Unknonw application command: ")
                    return result
                else:
                    result = func()
                    return result
            except TypeError:
                raise TypeError("Incorrect parameters for application command '" + method + "'. Parameters received: " + str(parameters))
                
        else:
            raise Exception("Unknown command: " + str(method))
 
    
    def executeInternalCommand(self, parsedLine):
        """
        This method executes an internal command
        @return: the execution result
        """
        command = parsedLine.getParameter(0).lower()
        internalCommand = self.internalCommands.getInternalCommandInstance(command)
        result = internalCommand.execute(self, parsedLine)
        return result

    def getFacade(self):
        """
        This method returns the python application facade received
        @return: the test facade
        """
        return self.facade